/**  
* Copyright (c) 2009, Noel Lynch All rights reserved. Redistribution and use  
* in source and binary forms, with or without modification, are permitted  
* provided that the following conditions are met: Redistributions of source  
* code must retain the above copyright notice, this list of conditions and the  
* following disclaimer. Redistributions in binary form must reproduce the above  
* copyright notice, this list of conditions and the following disclaimer in the  
* documentation and/or other materials provided with the distribution. Neither  
* the name of Noel Lynch nor the names of contributors may be used to  
* endorse or promote products derived from this software without specific prior  
* written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR  
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
// created by noellynch
// Jul 14, 2009

package com.sceneworker.app;

import java.util.ArrayList;

import javax.swing.tree.TreePath;

import com.acarter.scenemonitor.SceneMonitor;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingCapsule;
import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.jme.bounding.OrientedBoundingBox;
import com.jme.light.Light;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Line;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.TriMesh;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.MaterialState;
import com.jme.system.DisplaySystem;
import com.jme2utils.objects.AxesRep;
import com.jme2utils.objects.CircleRenderer;
import com.jme2utils.objects.LineCube;
import com.jme2utils.objects.RotationAxes;
import com.jme2utils.objects.ScaleAxes;
import com.jme2utils.objects.TranslationAxes;
import com.jme2utils.objects.TriRep;
import com.jme2utils.staticutils.BoundsUtils;
import com.jme2utils.staticutils.GenericJMEUtils;
import com.sceneworker.app.events.DelegateEventTypes;
import com.sceneworker.app.globals.SceneWorkerGlobals;
import com.sceneworker.app.globals.cacheobjects.RenderToTextureCacheObject;
import com.sceneworker.app.globals.cacheobjects.RootNodeCacheObject;
import com.sceneworker.app.globals.cacheobjects.TexturePainterCacheObject;
import com.sceneworker.app.input.SpatialSelectionHandler;
import com.sceneworker.util.delegation.DelegationEvent;
import com.sceneworker.util.delegation.DelegationEventBroadcaster;
import com.sceneworker.util.delegation.DelegationEventListener;

/**
 * Renders data specific to Scene Worker such as lights, selections etc...
 * @author noellynch
 *
 */
public class StandardJMERenderer implements DelegationEventListener {
	private static final int MAX_LIGHTS = 8;
	private		Node		mcl_crossHair;
    private		LineCube	mcl_selNode;
    private		LineCube	mcla_otherSelectionCubes[];
    private		Sphere		mcl_selectedLightSphere;
    private		Sphere		mcl_lightSphere;
    private		Text		mcl_debugText;
    private		Text		mcl_fpsText;
    private     TriRep      mcl_selectedTri;
    private		AxesRep		mcl_modifierAxes;
    
    private		TranslationAxes		mcl_txAxes;
    private		RotationAxes		mcl_rotAxes;
    private		ScaleAxes			mcl_scaleAxes;
    private		CircleRenderer		mcl_brushRenderer;
    
    private		int		mi_cntFrame;
    private		int		mi_lastCntFrame;
    private		float	mf_frameTimer;
    
    private		StandardJMEController		mcl_app;
    
    public	StandardJMERenderer(StandardJMEController cl_appHandler) {
    	mcl_app = cl_appHandler;
    	
    	buildCrossHair();

        initSelNode();

        initDebugText();

        initFPSText();

        initLightSphere();

        initSelTri();
        
        initModifierAxis();
        
        initBrushRenderer();
        
        DelegationEventBroadcaster.inst().addEventListener(DelegateEventTypes.NEW_TRI_SELECTION_EVT, this);
        DelegationEventBroadcaster.inst().addEventListener(DelegateEventTypes.NO_TRI_SELECTION_EVT, this);
    }
    
    private	void	initBrushRenderer() {
    	mcl_brushRenderer = new CircleRenderer("CircleRenderer");
    }
    
    public	AxesRep		getCurrentAxesRep() {
    	return mcl_modifierAxes;
    }
    
    public void handleEvent(DelegationEvent cl_e) {
        if(cl_e.getEventType().equals(DelegateEventTypes.NEW_TRI_SELECTION_EVT)) {
            TriMesh     lcl_mesh = (TriMesh)cl_e.getObjectData()[0];
            int         li_sel = (Integer)cl_e.getObjectData()[1];
            boolean     lb_inGLThread = (Boolean)cl_e.getObjectData()[2];
            mcl_selectedTri.setSelectedTriangle(lcl_mesh, li_sel, lb_inGLThread);
            return;
        }
        
        if(cl_e.getEventType().equals(DelegateEventTypes.NO_TRI_SELECTION_EVT)) {
            mcl_selectedTri.setNoTriangleSelection();
            return;
        }
    }
    
    public	Text	getDebugText() {
    	return mcl_debugText;
    }
    
    protected	void	handleTimePerFrame(float f_tpf) {
    	mi_cntFrame++;
        mf_frameTimer += f_tpf;

        if(mf_frameTimer > 1.0f) {
            mf_frameTimer = 0;
            mi_lastCntFrame = mi_cntFrame;
            mi_cntFrame = 0;
            mcl_fpsText.print("fps : " + mi_lastCntFrame);
        }
    }
    
    private	void	initModifierAxis() {
    	mcl_rotAxes = new RotationAxes();
    	mcl_txAxes = new TranslationAxes();
    	mcl_scaleAxes = new ScaleAxes();
    }

    private	void	initLightSphere() {
        mcl_selectedLightSphere = new Sphere("LightSphere", 5, 5, 7);
        MaterialState		lcl_yellowMat = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
        lcl_yellowMat.setDiffuse(ColorRGBA.yellow.clone());
        lcl_yellowMat.setEnabled(true);
        mcl_selectedLightSphere.setRenderState(lcl_yellowMat);
        mcl_selectedLightSphere.setLightCombineMode(LightCombineMode.Replace);
        mcl_selectedLightSphere.updateRenderState();
        
        mcl_lightSphere = new Sphere("LSpehere", 5, 5, 3);
        mcl_lightSphere.setSolidColor(ColorRGBA.yellow);
    	mcl_lightSphere.setLightCombineMode(LightCombineMode.Off);
    	mcl_lightSphere.updateRenderState();
    }

    private	void	initDebugText() {
        mcl_debugText = Text.createDefaultTextLabel("debugText");
        mcl_debugText.setLocalTranslation(10, 10, 0);
        mcl_debugText.print("cam location");
        mcl_debugText.setLocalScale(0.6f);
        mcl_debugText.updateRenderState();
        mcl_debugText.updateGeometricState(0, true);
    }

    private	void	initFPSText() {
        mcl_fpsText = Text.createDefaultTextLabel("fpsText");
        mcl_fpsText.setLocalTranslation(10, 470, 0);
        mcl_fpsText.print("");
        mcl_fpsText.setLocalScale(0.6f);
        mcl_fpsText.updateRenderState();
        mcl_fpsText.updateGeometricState(0, true);
    }

    private void    initSelTri() {
        mcl_selectedTri = new TriRep();
    }
    
    private	void	initSelNode() {
        mcl_selNode = new LineCube("selectionBox", LineCube.BLUE);
        mcla_otherSelectionCubes = new LineCube[10];

        for(int li_index = 0; li_index < mcla_otherSelectionCubes.length; li_index++) {
            mcla_otherSelectionCubes[li_index] = new LineCube("lineSel" + li_index, LineCube.YELLOW);
        }
    }

    private	void	buildCrossHair() {
        mcl_crossHair = new Node("crossHair");

        Vector3f		lcl_v[] = new Vector3f[2];

        int		li_w = DisplaySystem.getDisplaySystem().getWidth() >> 1;
        int		li_h = DisplaySystem.getDisplaySystem().getHeight() >> 1;
        lcl_v[0] = new Vector3f(li_w - 10, li_h, 0);
        lcl_v[1] = new Vector3f(li_w + 10, li_h, 0);

        Line			lcl_bounds = new Line("lineHud", lcl_v, null, null, null);
        lcl_bounds.setRenderQueueMode(Renderer.QUEUE_ORTHO);
        //lcl_bounds.setLightCombineMode(LightState.OFF);
        mcl_crossHair.attachChild(lcl_bounds);

        lcl_v = new Vector3f[2];
        lcl_v[0] = new Vector3f(li_w, li_h - 10, 0);
        lcl_v[1] = new Vector3f(li_w, li_h + 10, 0);

        lcl_bounds = new Line("lineHud2", lcl_v, null, null, null);
        lcl_bounds.setRenderQueueMode(Renderer.QUEUE_ORTHO);
        //lcl_bounds.setLightCombineMode(LightState.OFF);
        mcl_crossHair.attachChild(lcl_bounds);
        mcl_crossHair.updateRenderState();
        mcl_crossHair.updateGeometricState(0, true);
    }
    
    public	void	render(float f_tpf) {
    	handleTimePerFrame(f_tpf);
    	
    	// draw the cross hairs
    	DisplaySystem		lcl_display = DisplaySystem.getDisplaySystem();
    	
        lcl_display.getRenderer().draw(mcl_crossHair);

        // do scene worker render
        SceneMonitor.getMonitor().renderViewer(lcl_display.getRenderer());

        // do render to texture updates
        doRenderToTexture();

        // draw the selections
        drawSelections(lcl_display);

        // draw lights
        if(SceneWorkerGlobals.inst().getRenderLights()) {
        	drawLights(lcl_display);
        }
        

        // if a tri has been selected then draw it
        drawSelectedTri(lcl_display);
        
        // draw the currently selected modifer axis
        drawModifierAxis(lcl_display);
        
        // draw text outputs
        lcl_display.getRenderer().draw(mcl_debugText);
        lcl_display.getRenderer().draw(mcl_fpsText);
    }
    
    protected	void	drawModifierAxis(DisplaySystem cl_display) {
    	
    	if(mcl_app.getInputHandler().getSelectionHandler().getCurrentSelection() != null) {
    		boolean		lb_rotAxis = false;
    		
    		mcl_modifierAxes = null;
    		
    		switch(mcl_app.getInputHandler().getInputHandlerMode()) {
    		case	kObjectRotateX:
    		case	kObjectRotateY:
    		case	kObjectRotateZ:
    			if(mcl_app.getInputHandler().isMouseDown()) {
    				lb_rotAxis = true;
    			}
    			mcl_modifierAxes = mcl_rotAxes;
    			break;
    			
    		case	kObjectTranslationX:
    		case	kObjectTranslationY:
    		case	kObjectTranslationZ:
    			mcl_modifierAxes = mcl_txAxes;
    			break;
    			
    		case	kObjectScaleX:
    		case	kObjectScaleY:
    		case	kObjectScaleZ:
    			mcl_modifierAxes = mcl_scaleAxes;
    			break;	
    		}
    		
    		if(mcl_modifierAxes == null) {
    			return;
    		}
    		
    		Object		lcl_obj = mcl_app.getInputHandler().getSelectionHandler().getCurrentSelection();
    		
    		if(lcl_obj instanceof Spatial) {
    			Spatial			lcl_sel = (Spatial)lcl_obj;
    			Vector3f		lcl_max = BoundsUtils.getBoundAsVector(lcl_sel.getWorldBound());
        		mcl_modifierAxes.updateAxesSize(lcl_max.add(10, 10, 10));
        		if(lcl_sel.getWorldBound() != null) {
        			if(lcl_sel.getWorldBound().getCenter() != null) {
        				mcl_modifierAxes.setLocalTranslation(lcl_sel.getWorldBound().getCenter());
        			}
        		}
        		
        		if(lb_rotAxis) {
        			RotationAxes		lcl_rot = (RotationAxes)mcl_modifierAxes;
        			switch(mcl_app.getInputHandler().getInputHandlerMode()) {
            		case	kObjectRotateX:
            			cl_display.getRenderer().draw(lcl_rot.getXAxis());
            			break;
            			
            		case	kObjectRotateY:
            			cl_display.getRenderer().draw(lcl_rot.getYAxis());
            			break;
            			
            		case	kObjectRotateZ:
            			cl_display.getRenderer().draw(lcl_rot.getZAxis());
            			break;
        			}
        			
        		} else {
        			cl_display.getRenderer().draw(mcl_modifierAxes);
        		}
    		}
    		
    		
    		
    		//Debugger.drawBounds(mcl_modifierAxes, cl_display.getRenderer(), true);
    	}
    	
    }
    
    protected   void    drawSelectedTri(DisplaySystem cl_display) {
        if(mcl_selectedTri.getCurrentTriangleSelection() == -1) {
            return;
        }
        
        if(!mcl_app.getInputHandler().getSelectionHandler().getTriSelectMode()) {
            return;
        }
        
        if(mcl_app.getInputHandler().getSelectionHandler().getTrianglePaintMode()) {
        	TexturePainterCacheObject		lcl_co = (TexturePainterCacheObject)SceneWorkerGlobals.inst().getFromCache(TexturePainterCacheObject.TEXTURE_PAINTER);
        	if(lcl_co == null) {
        		return;
        	}
        	
        	float		lf_brushRadius = lcl_co.getTextureListerPanel().getBrush().getBrushRadius();
        	
        	Vector3f	lcl_n = mcl_selectedTri.getTriangle().getNormal();
            Vector3f	lcl_axis = Vector3f.UNIT_Z.cross(lcl_n);
            float		lf_a = FastMath.acos(Vector3f.UNIT_Z.dot(lcl_n));
            
            Quaternion			lcl_q = new Quaternion();
            lcl_q.fromAngleNormalAxis(lf_a, lcl_axis.normalizeLocal());
            
            mcl_brushRenderer.updateCircleDimension(lf_brushRadius);
            mcl_brushRenderer.setLocalRotation(lcl_q);
            mcl_brushRenderer.setLocalTranslation(mcl_selectedTri.getTriangle().getCenter());
            cl_display.getRenderer().draw(mcl_brushRenderer);
        }
        
        cl_display.getRenderer().draw(mcl_selectedTri);
    }
    
    protected	void	doRenderToTexture() {
        ArrayList<?>		lcla_rtt = SceneWorkerGlobals.inst().getAllInstancesInCache(RenderToTextureCacheObject.class);
        for(Object lcl_rtt : lcla_rtt) {
            RenderToTextureCacheObject		lcl_rttObj = (RenderToTextureCacheObject)lcl_rtt;
            if(lcl_rttObj.mcl_scenes != null) {
            	lcl_rttObj.mcl_rtt.render(lcl_rttObj.mcl_scenes, lcl_rttObj.mcl_texture);
            	/*if(lcl_rttObj.mcl_texture.getRTTSource() == RenderToTextureType.Depth) {
            		lcl_rttObj.mcl_rtt.render(lcl_rttObj.mcl_scenes, lcl_rttObj.mcl_texture, true);
            	} else {
            		
            	}*/
            	
            }
        }
    }

    protected	void	drawLights(DisplaySystem cl_display) {
    	ArrayList<Light>	lcla_lights = new ArrayList<Light>();
    	
    	Node		lcl_n = (Node)SceneWorkerGlobals.inst().getFromCache(RootNodeCacheObject.ROOT_NODE).getValue();
    	GenericJMEUtils.findAllLights(lcl_n, lcla_lights);
    	for(Light lcl_o : lcla_lights) {
    		if(lcl_o instanceof PointLight) {
    			PointLight		lcl_light = (PointLight)lcl_o;
    			
    			mcl_lightSphere.setLocalTranslation(lcl_light.getLocation());
    			cl_display.getRenderer().draw(mcl_lightSphere);    			
    		}
    	}
    	
        TreePath		lcl_treePath = SceneMonitor.getMonitor().getMonitorTree().getSelectionPath();
        if(lcl_treePath == null) { return; }
        Object			lcl_obj = lcl_treePath.getLastPathComponent();

        if(lcl_obj instanceof PointLight) {
            PointLight		lcl_ptLight = (PointLight)lcl_obj;

            Vector3f		lcl_loc = lcl_ptLight.getLocation();
            mcl_selectedLightSphere.setLocalTranslation(lcl_loc.clone());
            mcl_selectedLightSphere.updateRenderState();
            cl_display.getRenderer().draw(mcl_selectedLightSphere);
        }
    }

    protected	void	drawSelections(DisplaySystem cl_display) {
        if(mcl_app.getInputHandler().getSelectionHandler().getCurrentSelection() != null) {
        	Object		lcl_obj = mcl_app.getInputHandler().getSelectionHandler().getCurrentSelection();
        	
        	if(lcl_obj instanceof Spatial) {
        		Spatial		lcl_sel = (Spatial)lcl_obj;
                transformAndDrawSelectionCube(lcl_sel, mcl_selNode, cl_display);

                if(mcl_app.getInputHandler().getSelectionHandler() instanceof SpatialSelectionHandler) {
                	ArrayList<Spatial>		lcla_otherSelections = ((SpatialSelectionHandler)mcl_app.getInputHandler().getSelectionHandler()).getOtherSelections();

                    for(int li_index = 0; li_index < lcla_otherSelections.size(); li_index++) {
                        if(li_index < mcla_otherSelectionCubes.length) {
                            transformAndDrawSelectionCube(lcla_otherSelections.get(li_index), mcla_otherSelectionCubes[li_index], cl_display);
                        }
                    }
                }
                
        	}
            
        }
    }

    private	void	transformAndDrawSelectionCube(Spatial cl_selectedObject, LineCube cl_selectionCube, DisplaySystem cl_display) {
        if(cl_selectedObject == null) { return; }

        if(cl_selectedObject.getRenderQueueMode() == Renderer.QUEUE_ORTHO) {
        	cl_selectionCube.setRenderQueueMode(Renderer.QUEUE_ORTHO);
        } else {
        	cl_selectionCube.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
        }
        
        if(cl_selectedObject.getWorldBound() != null) {
            BoundingVolume		lcl_bv = cl_selectedObject.getWorldBound();
            Vector3f			lcl_tx = new Vector3f(lcl_bv.getCenter());
            cl_selectionCube.setLocalTranslation(lcl_tx);
            cl_selectionCube.setLocalScale(new Vector3f(1, 1, 1));

            if(lcl_bv instanceof BoundingSphere) {
                float		lf_r = ((BoundingSphere)lcl_bv).radius;
                cl_selectionCube.setLocalScale(new Vector3f(lf_r, lf_r, lf_r));
            } else if(lcl_bv instanceof BoundingBox){
                Vector3f		lcl_extent = ((BoundingBox)lcl_bv).getExtent(null);
                cl_selectionCube.setLocalScale(lcl_extent);
            } else if(lcl_bv instanceof OrientedBoundingBox){
                Vector3f		lcl_extent = new Vector3f(((OrientedBoundingBox)lcl_bv).getExtent());
                cl_selectionCube.setLocalScale(lcl_extent);
            } else if(lcl_bv instanceof BoundingCapsule) {
                float		lf_r = ((BoundingCapsule)lcl_bv).getRadius();
                cl_selectionCube.setLocalScale(new Vector3f(lf_r, lf_r, lf_r));
            }
            
            cl_selectionCube.updateRenderState();
            cl_selectionCube.updateGeometricState(0, true);
            cl_display.getRenderer().draw(cl_selectionCube);
        }
    }
}
